#coding=utf-8

#########################################################
#                      Verzhak                          #
#              e-mail - <verzhak@gmail.com>             #
#              radist-elvin.blogspot.com                #
#########################################################
#       Version - 0.0.1                                 #
#       Date - 12.08.2008                               #
#       License - GPLv3                                 #
#########################################################

import time,stat,os,fuse,errno

import urllib

fuse.fuse_python_api = (0,2)

class BashOrgNet():
	def __init__(self):
		self.__cite = {}
		self.__page = {}
		self.__rss = ''
		self.__rss_xml = ''
		#Количество цитат
		self.__citeNum = -1
		#Количество страниц
		self.__pageNum = -1
		#Предыдущие обновления информации (в секундах)
		self.__prevCiteNum = -1
		self.__prevPageNum = -1
		self.__prevRSS = -1
		#Задержка между обновлениями информации (в секундах)
		self.__maxPrev = 1200

	def __replace__(self,str):
		# Заменить xml-код на нужные символы
		rpl = {'<br>' : '\n','&lt;' : '<','&gt;' : '>', '&laquo;' : '<<' , '&raquo;' : '>>', '&copy;' : '(c)' , '&reg;' : '(tm)', '&nbsp;' : ' '}
		for x in rpl.keys():
				str = str.replace(x,rpl[x])
		return str

	def getCite(self,num):
		# Получаем цитату
		if (not self.__cite.has_key(num)):
			cite = urllib.urlopen('http://bash.org.ru/quote/'+str(num))
			gt = 0
			mark = '<a href="/quote/' + str(num) + '/bayan"'
			self.__cite[num] = ''
			next = cite.readline()
			while next:
				if next.find(mark) != -1:
					gt = 1
				self.__cite[num] = self.__cite[num] + next
				next = cite.readline()
			if not gt:
				# Цитата не найдена
				self.__cite[num] = 'Cite not found'
			cite.close()
		return self.__cite[num]
	
	def getPage(self,num):
		if (not self.__page.has_key(num) or num >= self.__pageNum):
			#Обновляем данные о количестве страниц
			self.getPageNum()
			if num <= self.getPageNum():
				page = urllib.urlopen('http://bash.org.ru/index/'+str(num))
				self.__page[num] = ''
				next = page.readline()
				while next:
					self.__page[num] = self.__page[num] + next
					next = page.readline()
				page.close()
			else:
				# Страница не найдена
				self.__page[num] = 'Page not found'
		return self.__page[num]
	
	def getRSS(self):
		# Получаем и парсим rss-ленту
		tm = time.time()
		if (self.__prevRSS == -1 or tm - self.__prevRSS > self.__maxPrev):
			self.__rss = ''
			self.__rss_xml = ''
			rss = urllib.urlopen('http://bash.org.ru/rss/')
			next = rss.readline()
			while next:
				# В зависимости от содержимого полученной строки - записываем в результирующую строку нужные данные
				# Да, да - я подозреваю о существовании XMLReader и иже с ними, но моим способом парсить ленту проще
				# Хотя, конечно, разработчикам BOR достаточно внести малейшие поправки в формат - и функцию предется переписывать
				# Да и черт с ним - перепишу
				self.__rss_xml = self.__rss_xml + next
				if next.find('<item>') != -1:
					next = '############################################################################\n'
				elif next.find('<title>') != -1:
					next = next[next.find('<title>') + 7 : next.find('</title>')] + '\n'
				elif next.find('<pubDate>') != -1:
					next = next[next.find('<pubDate>') + 9 : next.find('</pubDate>')] + '\n'
				elif next.find('<description><![CDATA[') != -1:
					next = next[next.find('<description><![CDATA[') + 22 : next.find('</description>') - 3] + '\n'
				else:
					next = ''
				self.__rss = self.__rss + next
				next = rss.readline()
			self.__rss = self.__rss + '############################################################################\n' 
			rss.close()
			# Теперь заменим "специальные" последовательности
			self.__rss = self.__replace__(self.__rss)
			self.__prevRSS = tm
		return self.__rss
	
	def getRSSXML(self):
		self.getRSS()
		return self.__rss_xml

	def getCiteNum(self):
		# Получаем количество цитат
		tm = time.time()
		if (self.__prevCiteNum == -1 or tm - self.__prevCiteNum > self.__maxPrev):
			citeNum = urllib.urlopen('http://bash.org.ru/')
			next = citeNum.readline()
			while next:
				if next.find('<a href="/quote/') != -1:
					self.__citeNum = int(next[next.find('<a href="/quote/') + 16 : next.find('">')])
					break
				next = citeNum.readline()
			citeNum.close()
			self.__prevCiteNum = tm
		return self.__citeNum

	def getPageNum(self):
		# Получаем количество страниц с цитатами
		tm = time.time()
		if (self.__prevPageNum == -1 or tm - self.__prevPageNum > self.__maxPrev):
			pageNum = urllib.urlopen('http://bash.org.ru/')
			next = pageNum.readline()
			while next:
				if next.find('<a href="/index/') != -1:
					temp = next[next.find('<a href="/index/') + 16 : next.find('</a>')]
					temp = temp[temp.find('">') + 2 : ]
					self.__pageNum = int(temp) + 1
					break
				next = pageNum.readline()
			pageNum.close()
			self.__prevPageNum = tm
		return self.__pageNum
	
	def getPagesExist(self):
		return self.__page.keys()

	def getCitesExist(self):
		return self.__cite.keys()

class BashOrgStat(fuse.Stat):
	def __init__(self):
		self.st_mode = 0
		self.st_ino = 0
		self.st_dev = 0
		self.st_nlink = 0
		self.st_uid = 0
		self.st_gid = 0
		self.st_size = 0
		self.st_atime = time.time()
		self.st_mtime = time.time()
		self.st_ctime = time.time()

class BashOrgFS(fuse.Fuse,BashOrgNet):
	def __init__(self, *args, **kw):
		fuse.Fuse.__init__(self, *args, **kw)
		BashOrgNet.__init__(self)
		self.README = """
#########################################################
#                      Verzhak                          #
#              e-mail - <verzhak@gmail.com>             #
#              radist-elvin.blogspot.com                #
#########################################################
#       Version - 0.0.1                                 #
#       Date - 12.08.2008                               #
#       License - GPLv3                                 #
#########################################################

BashOrgFS - проект, с помощью которого можно представить bash.org.ru в виде (виртуальной) файловой системы. Проект использует fuse, python и fuse-python.

fuse-python вы может получить здесь: http://fuse.sourceforge.net/wiki/index.php/FusePython

Использование (MP - точка монтирования):

./bashorg MP	-	монтирование
MP/cite			-	цитаты с bash.org.ru. Каждая цитата содержится в файле с ее номером (там лежит html-страница с цитатой). При этом в текущем листинге отображаются уже загруженные цитаты (это, всегда, 1-я и самая последняя из имеющихся на BOR'е (соотв. самая последняя имеет наибольший номер). Чтобы просмотреть цитату с номером XXX сделайте, например: elinks XXX. В последующих листингах цитата будет присутствовать
MP/page			-	то же, но для страниц. При этом при каждом листинге последняя страница будет загружаться заново (!)
MP/rss			-	"пропарсенный" rss
MP/rss_xml		-	rss в формате xml
MP/README		-	текущий README
su -c 'umount MP'

(!) Используйте программу на свой страх и риск
(!!) Обо всех найденных вами ошибках сообщайте мне на e-mail
"""

	def getattr(self, path):
		# Определяем аттрибуты каталога/файла path
		st = BashOrgStat()
		if (path == '/' or path == '/page' or path == '/cite'):
			# path - каталог
			st.st_mode = stat.S_IFDIR | 0555
			st.st_nlink = 2
		else:
			flag = 0
			# Определяем, является ли path файлом
			if path in ['/rss','/rss_xml','/README']:
				flag = 1
				if path == '/rss':
					st.st_size = len(self.getRSS())
				elif path == '/README':
					st.st_size = len(self.README)
				else:
					st.st_size = len(self.getRSSXML())
			else:
				try:
					dir = path[:5]
					file = int(path[6:])
					if (dir == '/cite' and file >= 1 and file <= self.getCiteNum()):
						flag = 1
						st.st_size = len(self.getCite(file))
					elif (dir == '/page' and file >= 1 and file <= self.getPageNum()):
						flag = 1
						st.st_size = len(self.getPage(file))
				except:
					#Исключение вызвано, если в int() было передано не число
					pass
			if flag == 1:
				# Да, path - файл
				st.st_mode = stat.S_IFREG | 0444
				st.st_nlink = 1
			else:
				# path - не файл
				# (path не найден)
				return -errno.ENOENT
		return st

	def readdir(self, path, offset):
		# Выводим содержимое каталога path
		if path == '/':
			for x in  ['.', '..','page','cite','rss','rss_xml','README']:
				yield fuse.Direntry(x)
		elif path == '/page':
			pagesExist = self.getPagesExist()
			for x in ['.','..'] + pagesExist:
				yield fuse.Direntry(str(x))
			if 1 not in pagesExist:
				yield fuse.Direntry('1')
			if self.getPageNum() not in pagesExist:
				yield fuse.Direntry(str(self.getPageNum()))
		elif path == '/cite':
			citesExist = self.getCitesExist()
			for x in ['.','..'] + citesExist:
				yield fuse.Direntry(str(x))
			if 1 not in citesExist:
				yield fuse.Direntry('1')
			if self.getPageNum() not in citesExist:
				yield fuse.Direntry(str(self.getCiteNum()))
		else:
			yield fuse.Direntry('ERROR')

	def open(self,path,flags):
		# Проверяем, существует ли файл path в нашей ФС
		flag = 0
		if path in ['/rss','/rss_xml','/README']:
			flag = 1
		else:
			try:
				dir = path[:5]
				file = int(path[6:])
				if (dir == '/cite' and file >= 1 and file <= self.getCiteNum()):
					flag = 1
				elif (dir == '/page' and file >= 1 and file <= self.getPageNum()):
					flag = 1
			except:
				# Исключение вызвано, если в int() было передано не число
				pass
		if flag == 1:
			# Файл существует - проверяем, как открывают файл
			# (доступно только ReadOnly)
			accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
			if (flags & accmode) != os.O_RDONLY:
				return -errno.EACCES
		else:
			# Файл не существует - ошибка
			return -errno.ENOENT

	def read(self,path,size,offset):
		str = 'ENOENT'
		if path == '/rss':
			str = self.getRSS()
		elif path == '/rss_xml':
			str = self.getRSSXML()
		elif path == '/README':
			str = self.README
		else:
			try:
				dir = path[:5]
				file = int(path[6:])
				if (dir == '/cite' and file >= 1 and file <= self.getCiteNum()):
					str = self.getCite(file)
				elif (dir == '/page' and file >= 1 and file <= self.getPageNum()):
					str = self.getPage(file)
			except:
				#Исключение вызвано, если в int() было передано не число
				pass
		if str != 'ENOENT':
			# Файл существует - возвращаем его содержимое
			slen = len(str)
			if offset < slen:
				if offset + size > slen:
					size = slen - offset
				buf = str[offset:offset+size]
			else:
				buf = ''
			return buf
		else:
			# Файл не существует - ошибка
			return -errno.ENOENT

	def statfs(self):
		st = fuse.StatVfs()
		st.f_bsize = 1024
		st.f_frsize = 1024
		st.f_bfree = 0
		st.f_bavail = 0
		st.f_files = 8 + self.getPageNum() + self.getCiteNum()
		st.f_blocks = 4
		st.f_ffree = 0
		st.f_favail = 0
		st.f_namelen = 255
		return st

def createBashOrgFS():
	bo = BashOrgFS(version="%prog " + fuse.__version__,dash_s_do='setsingle')
	bo.parse(errex=1)
	bo.main()
